/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.html;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Locale;
import java.util.StringTokenizer;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

import be.destin.beans.Reflector;
import be.destin.rdf.changes.Status;
import be.destin.rdf.changes.StatusInterface;
import be.destin.skos.core.Concept;
import be.destin.skos.core.ConceptScheme;
import be.destin.skos.core.LinkedConcept;
import be.destin.skos.core.LocalLinkedConcept;
import be.destin.skos.core.NoScheme;
import be.destin.skos.core.Predicate;
import be.destin.skos.core.Profile;
import be.destin.skos.core.SchemeInterface;
import be.destin.skos.core.SkosManager;
import be.destin.skos.core.SkosUtil;
import be.destin.skos.core.TermList;
import be.destin.skos.core.UrlList;
import be.destin.skos.search.LabelMatchType;
import be.destin.skos.search.SkosSearch;
import be.destin.util.Util;

public class SkosHtmlManager {

	// SkosSurroundings CSS class=border-collapse:collapse; border:1px solid black; background:white;
	
	/** log4j logger */
	private static Logger log = Logger.getLogger(SkosHtmlManager.class);

	public static final String askosiMessagesScheme = "askosiMessages";
	public static final String askosiDisplayApplication = "scheme";
	public static final String askosiDisplayUrl = "[root]/concept.jsp?concept=[scheme_About]";
	
	public static final String SKOSdirectoryParam = "SKOSdirectory";
	public static final String SKOSpreloadParam = "SKOSpreload";

	public static final String GraphVizDirectoryParam = "GraphVizDirectory";
	public static final String GraphVizExecParam = "GraphVizExec";

	public static final String prefSource = " [<i>";
	public static final String suffSource = "</i>]";
	
	public static final String prefRelationQualifier = "<i>";
	public static final String suffRelationQualifier = "</i>&nbsp;";
	
	//public static final String askosiConceptIcon = "[root"+askosiDisplayApplication+"]/image/concept16.png";
	public static final String framingUrlSuffix = "framed=yes";

	private HttpServletRequest request;
	private SkosManager skosManager;
	private String application;
	private String userLanguage = TermList.ENGLISH;
	private boolean framedInAskosi = false;

	private String userProfile = Profile.guest; // "guest" is the default profile for data access

	private SkosHtmlManager(HttpServletRequest request, String userLanguage, String application) {
		super();
		this.request = request;
		String framed = request.getParameter("framed");
		String independant = request.getParameter("independant");
		framedInAskosi = (framed != null && "yes".equalsIgnoreCase(framed));
		if (independant != null && "yes".equalsIgnoreCase(independant)) framedInAskosi = false;
		this.application = application;
		if (userLanguage == null || userLanguage.isEmpty()) {
			this.userLanguage = this.getRequestLanguage();
		} else {
			this.userLanguage = userLanguage;
		}
		this.getRequestProfile();
	}

	public SkosHtmlManager(HttpServletRequest request, String userLanguage, SkosManager skosManager, String application) {
		this(request,userLanguage,application);
		this.skosManager = skosManager;
		this.skosManager.initApplication(application, Util.getURLroot(request));
	}

	private void initApp(HttpServletRequest request, String application) {
		
		if (this.application == null || this.application.isEmpty()) {
			this.application = SkosHtmlManager.askosiDisplayApplication;
		}
		String host = null;
		if (this.application == SkosHtmlManager.askosiDisplayApplication) {
			host = Util.getInitParameter(request,"base");
		}
		if (host == null) {
			host = Util.getURLroot(request);
		}
		this.skosManager.initApplication(this.application, host);
	}

	public SkosHtmlManager(HttpServletRequest request, String userLanguage, String configDirectory,String application) {
		this(request,userLanguage,application);
		this.skosManager = SkosManager.getInstance(configDirectory);
		initApp(request,application);
	}

	public SkosHtmlManager(String userLanguage, SkosManager skosManager, String application, String applicationURL) {
		this.application = application;
		if (this.application == null || this.application.isEmpty()) {
			this.application = SkosHtmlManager.askosiDisplayApplication;
		}
		this.userLanguage = userLanguage;
		this.skosManager = skosManager;
		this.skosManager.initApplication(this.application, applicationURL);
	}

	/* Fast temporary SkosHtmlManager */
	public SkosHtmlManager(HttpServletRequest request, NoScheme aScheme) {
		super();
		framedInAskosi = false;
		this.request = request;
		this.skosManager = aScheme.getManagerInstance();
		this.application = aScheme.getApplication();
		if (this.application == null || this.application.isEmpty()) {
			this.application = SkosHtmlManager.askosiDisplayApplication;
		}
		initApp(request,application);
		this.userLanguage = this.getRequestLanguage();
		this.getRequestProfile();
	}

	public String getAdaptedUrl(NoScheme aScheme, UrlList urlList) {
		if (urlList == null) return "";
		String userLanguage = this.getLanguage();
		String anUrl = urlList.getLing(userLanguage);
		if (anUrl == null || anUrl.length()==0) return "";
		String application = this.getApplication();
		if (application == null || application.length()==0) {
			application = this.getApplication();
		}
		String applicationRoot = this.getApplicationRoot(application);
		return Reflector.substituteURL(applicationRoot, aScheme, anUrl, userLanguage);
	}

	public String getAdaptedUrl(Concept aConcept, UrlList urlList) {
		if (urlList == null) return "";
		String userLanguage = this.getLanguage();
		String anUrl = urlList.getLing(userLanguage);
		if (anUrl == null || anUrl.length()==0) return "";
		String application = this.getApplication();
		if (application == null || application.length()==0) {
			application = this.getApplication();
		}
		String applicationRoot = this.getApplicationRoot(application);
		return Reflector.substituteURL(applicationRoot, new Object[]{aConcept,aConcept.getInScheme()}, anUrl, userLanguage);
	}

	public String toHtmlScheme_Concept(String input, SchemeInterface defaultList){
		// log.debug("Explain "+input+" in "+language+", default="+defaultList.toString());
		StringBuffer output = new StringBuffer();
		boolean subst = false;
		int precPos = 0;
		int pos = input.indexOf(SchemeInterface.separator);
		while (pos >= 0) {
			int i = pos-1;
			while (i >= 0 && Character.isJavaIdentifierPart(input.charAt(i))) {
				i--;
			}
			i++;
			output.append(input.substring(precPos, i));
			int j = pos+1;
			while (j < input.length() && Character.isJavaIdentifierPart(input.charAt(j))) {
				j++;
			}
			String fullHandle = input.substring(i,j);
			log.debug("toHTML "+fullHandle);
			Concept aConcept = skosManager.getScheme_Concept(fullHandle);
			if (aConcept == null) {
				output.append(fullHandle);
			}
			else {
				if (aConcept.getInScheme() == skosManager.getPrefixes()) {
					output.append(aConcept.getPrefLabel(this.userLanguage, strictLanguage));
				}
				else if (aConcept.getInScheme() == skosManager.getSuffixes()) {
					output.append(aConcept.getPrefLabel(this.userLanguage, strictLanguage));
				}
				else {
					output.append(aConcept.toHtmlLabel(this));
					subst = true;
				}
			}
			precPos = j;
			pos = input.indexOf(SchemeInterface.separator, precPos);
		}
		if (subst) return output.append(input.substring(precPos)).toString();
		if (defaultList == null) return input;
		Concept aConcept = defaultList.getConcept(SkosUtil.normalizeAbout(input));
		if (aConcept == null) return input;
		return aConcept.toHtmlLabel(this);
	}

	public String toHtmlScheme_ConceptInAction(String input, SchemeInterface defaultList, String action){
		// log.debug("Explain "+input+" in "+language+", default="+defaultList.toString());
		StringBuffer output = new StringBuffer();
		boolean subst = false;
		int precPos = 0;
		int pos = input.indexOf(SchemeInterface.separator);
		while (pos >= 0) {
			int i = pos-1;
			while (i >= 0 && Character.isJavaIdentifierPart(input.charAt(i))) {
				i--;
			}
			i++;
			output.append(input.substring(precPos, i));
			int j = pos+1;
			while (j < input.length() && Character.isJavaIdentifierPart(input.charAt(j))) {
				j++;
			}
			String fullHandle = input.substring(i,j);
			log.debug("toHTML "+fullHandle);
			Concept aConcept = skosManager.getScheme_Concept(fullHandle);
			if (aConcept == null) {
				output.append(fullHandle);
			}
			else {
				if (aConcept.getInScheme() == skosManager.getPrefixes()) {
					output.append(aConcept.getPrefLabel(this.userLanguage, strictLanguage));
				}
				else if (aConcept.getInScheme() == skosManager.getSuffixes()) {
					output.append(aConcept.getPrefLabel(this.userLanguage, strictLanguage));
				}
				else {
					output.append(aConcept.toHtmlLabelSimple(this,true));
					if (action != null && !action.isEmpty()) {
						String toDo = Util.replaceAllNoRegExp(action, "[about]", fullHandle);
						toDo = Util.replaceAllNoRegExp(toDo, "[begin]", ""+i);
						toDo = Util.replaceAllNoRegExp(toDo, "[end]", ""+j);
						output.append(toDo);
					}
					subst = true;
				}
			}
			precPos = j;
			pos = input.indexOf(SchemeInterface.separator, precPos);
		}
		if (subst) return output.append(input.substring(precPos)).toString();
		if (defaultList == null) return input;
		Concept aConcept = defaultList.getConcept(SkosUtil.normalizeAbout(input));
		if (aConcept == null) return input;
		return aConcept.toHtmlLabel(this);
	}

	/**
	 * Analyze and HTML fragment and "decorates" the references to ASKOSI managed concepts
	 * 
	 * @param input: HTML string to process
	 * @param actionOutside: decoration for a concept reference outside a link: NOT FULLY IMPLEMENTED YET, always the label text with a link to ASKOSI is generated
	 * @param actionInside: decoration for a concept reference inside a link: NOT FULLY IMPLEMENTED YET, always the label text only is put in place
	 * @return: Decorated HTML string
	 */
	public String toDecoratedHtml(String input, String actionOutside, String actionInside){
		// log.debug("Explain "+input+" in "+language+", default="+defaultList.toString());
		StringBuffer output = new StringBuffer();
		StringBuffer word = new StringBuffer();
		boolean inTag = false;
		boolean begTag = false;
		boolean slashTag = false;
		boolean inTagA = false;
		boolean inTagOBJECT = false;
		boolean inAttrib = false;
		boolean inAttrib2 = false;
		for (char c: input.toCharArray() ) {
			boolean endWord = false;
			boolean translate = !inTag;
			if (translate) {
				slashTag = false;
				begTag = false;
				inAttrib = false;
				inAttrib2 = false;
			}
			if (c == '"') {
				if (inTag && !inAttrib2) {
					inAttrib = !inAttrib;
				}
				endWord = true;
			} else if (c == '\'') {
				if (inTag && !inAttrib) {
					inAttrib2 = !inAttrib2;
				}
				endWord = true;
			} else if (c == '<') {
				if (!inAttrib && !inAttrib2) {
					inTag = true;
					begTag = true;
				}
				endWord = true;
			} else if (c == '>') {
				if (!inAttrib && !inAttrib2) {
					inTag = false;
				}
				endWord = true;
			} else if (c == '/') {
				if (inTag && !inAttrib && !inAttrib2) {
					slashTag = true;
				}
				endWord = true;
			} else if (Character.isJavaIdentifierPart(c)) {
				word.append(c);
			} else {
				endWord = true;
			}
			if (endWord) {
				if (word.length() > 0) {
					String sWord = word.toString();
					if (translate) {
						if (sWord.indexOf(SchemeInterface.separator) >= 0) {
							Concept aConcept = skosManager.getScheme_Concept(sWord);
							if (aConcept == null) {
								output.append(word);
							}
							else {
								if (aConcept.getInScheme() == skosManager.getPrefixes()) {
									output.append(aConcept.getPrefLabel(this.userLanguage, strictLanguage));
								}
								else if (aConcept.getInScheme() == skosManager.getSuffixes()) {
									output.append(aConcept.getPrefLabel(this.userLanguage, strictLanguage));
								}
								else {
									if (inTagA || inTagOBJECT) {
										output.append(aConcept.getPrefLabel(this.userLanguage, strictLanguage));
									} else {
										output.append(aConcept.toHtmlLabel(this));
									}
								}
							}
						} else {
							output.append(word);
						}

					} else {
						if (begTag) {
							if (sWord.equalsIgnoreCase("A")) {
								inTagA = !slashTag;
							} else if (sWord.equalsIgnoreCase("OBJECT")) {
								inTagOBJECT = !slashTag;
							} else {
								// other tags: no effect?
							}
							begTag = false;
						}
						output.append(word);
					}
					word = new StringBuffer();
				}
				output.append(c);
			}
		}
		return output.toString();
	}

	//public String adaptURL(String anURL){
	//	return adaptURL(this.application,anURL);
	//}

	public String adaptURL(String application, String anURL){
		if (anURL==null) return "";
		String result = anURL;
		if (anURL.indexOf("[root]") >= 0) {
			String replacement = this.getApplicationRoot(application);
			result = Util.replaceAllNoRegExp(anURL, "[root]",replacement);
		}
		int posR = result.indexOf("[root");
		if (posR >= 0) {
			int posEnd = result.indexOf("]",posR);
			if (posEnd > posR+5) {
				application = result.substring(posR+5,posEnd);
			}
			String replacement = this.getApplicationRoot(application);
			result = Util.replaceAllNoRegExp(result, "[root"+application+"]",replacement);
		}
		return result;
	}

	public String toHtmlImage(String icon, String name, boolean dimmed) {
	    if (icon == null || icon.length() == 0 ) return "";
	    icon = this.adaptURL(this.getApplication(),icon);
	    if (icon.startsWith("html:")) {
	    	return "<span class=\"skosSymbol"
	        	+(dimmed?"Dimmed":"")
	        	+ "\""
	      		+ ( name != null
		        		   ? " title=\""+Util.escapeHTMLEntities(name)+"\""
		        		   : "" )
		        + ">"
	        	+icon.substring(5)//characters after "html:"
	        	+"</span>";
	    }
	    else {
	    	return "<img src=\"" + icon  + "\" border=\"0\" "
	      		+ ( name != null
	        		   ? "title=\""+Util.escapeHTMLEntities(name)+"\" "
	        		   : "" )
	        	+ "class=\"skosIcon"
	        	+(dimmed?"Dimmed":"")
	        	+"\"/>"; 
	    }
	}

	public String toHtmlImageID(String icon, boolean dimmed, String id) {
	    if (icon == null || icon.length() == 0 ) return "";
	    icon = this.adaptURL(this.getApplication(),icon);
	    if (icon.startsWith("html:")) {
	    	return "<span class=\"skosSymbol"
	        	+(dimmed?"Dimmed":"")
	        	+ ( id != null && !id.isEmpty()
	        		   ? " id=\""+id+"\" "
	        		   : "" )
	        	+"\">"
	        	+icon.substring(5)//characters after "html:"
	        	+"</span>";
	    }
	    else {
	    	return "<img src=\"" + icon  + "\" border=\"0\" "
	      		+ ( id != null && !id.isEmpty()
	        		   ? " id=\""+id+"\" "
	        		   : "" )
	        	+ "class=\"skosIcon"
	        	+(dimmed?"Dimmed":"")
	        	+"\"/>"; 
	    }
	}

	public String toHtmlImageName(String icon, boolean dimmed, String name) {
	    if (icon == null || icon.length() == 0 ) return "";
	    icon = this.adaptURL(this.getApplication(),icon);
	    if (icon.startsWith("html:")) {
	    	return "<span class=\"skosSymbol"
	        	+(dimmed?"Dimmed":"")
	        	+ ( name != null && !name.isEmpty()
	        		   ? " name=\""+name+"\" "
	        		   : "" )
	        	+"\">"
	        	+icon.substring(5)//characters after "html:"
	        	+"</span>";
	    }
	    else {
	    	return "<img src=\"" + icon  + "\" border=\"0\" "
	      		+ ( name != null && !name.isEmpty()
	        		   ? " name=\""+name+"\" "
	        		   : "" )
	        	+ "class=\"skosIcon"
	        	+(dimmed?"Dimmed":"")
	        	+"\"/>"; 
	    }
	}

	public String getApplicationRoot (String application) {
		String currRoot = this.skosManager.getApplicationRoot(application);
		if (currRoot != null) return currRoot;//.toExternalForm();
		if (this.request != null) {
			return this.request.getContextPath();
		}
		return "";
	}

	public String getApplicationRoot () {
		return getApplicationRoot(this.application);
	}

	public String toHtmlRole(String application, String aRole, Object contextObject) {
		ConceptScheme theRoles = skosManager.getRoles();
		//if (application == null) application = this.application;
		if (theRoles == null) return aRole;
		Concept conceptRole = theRoles.getConcept(aRole);
		if (conceptRole == null) return aRole;
		String label = conceptRole.getPrefLabel(this.userLanguage, strictLanguage);
		if (label != null && !label.isEmpty()) label = label+":";
		String icon = conceptRole.getIconAdapted(this);
		if (icon != null && !icon.isEmpty()) {
			return toHtmlImage(icon, label, false);
		} else if (label != null) {
			return Util.escapeHTMLEntities(label);
		} else  {
			return aRole;
		}
	}

	public String toHtmlCategory(String application,
								 String aCategory, boolean isPure, Object contextObject) {
		String icon = "";
		if (aCategory==null) {
			aCategory = "";
		}
		ConceptScheme theCategories = skosManager.getCategories();
		if (theCategories == null) {
			return aCategory;
		}
		Concept conceptCategory;
		if (!aCategory.isEmpty()) {
			conceptCategory = theCategories.getConcept(aCategory);
		} else {
			conceptCategory = theCategories.getConcept("everything");
		}
		if (conceptCategory == null) return aCategory;
		String result = "";
		String label = conceptCategory.getPrefLabel(this.userLanguage, strictLanguage);
		if (label != null && !label.isEmpty()) label = label+":";
		icon = conceptCategory.getIconAdapted(this);
		//if (application == null) application = this.application;
		if (icon != null && !icon.isEmpty()) {
			result = toHtmlImage(icon, label, ! isPure);
		}
		if (result.isEmpty()) {
			result = Util.escapeHTMLEntities(label);
			if (result.isEmpty()) {
				result = aCategory;
			}
		}
		return result;
	}

	public String toHtmlApplication(String application, Object contextObject) {
		return toHtmlApplication(application, contextObject, true);
	}

	public String toHtmlApplicationName(String application, Object contextObject) {
		return toHtmlApplication(application, contextObject, false);
	}

	public String toHtmlApplication(String application, Object contextObject, boolean iconOnly) {
		if (application == null) application = this.application;
		ConceptScheme theApplications = skosManager.getApplications();
		if (theApplications == null) return application;
		Concept conceptApplication = theApplications.getConcept(application);
		if (conceptApplication == null) return application;
		String result = "";
		String label = conceptApplication.getPrefLabel(this.userLanguage, strictLanguage);
		if (label == null) {
			label = "";
		}
		if (label.isEmpty()) {
			label = application;
		}
		if (iconOnly) {
			label = label + ":";
		}
		String icon = conceptApplication.getIconAdapted(this);
		if (icon != null && !icon.isEmpty()) {
			result = toHtmlImage(icon, label, false)
			          + (iconOnly?"":label);
		}
		if (result.isEmpty()) {
			return Util.escapeHTMLEntities(label);
		}
		else {
			return result;
		}
	}

	public String toHtmlRelationQualifier(String relationQualifier) {
		if (relationQualifier == null || relationQualifier.isEmpty()) return "";
		ConceptScheme theRoles = skosManager.getRelationQualifiers();
		//if (application == null) application = this.application;
		if (theRoles == null) return prefRelationQualifier+relationQualifier+suffRelationQualifier;
		Concept conceptRole = theRoles.getConcept(relationQualifier);
		if (conceptRole == null) return prefRelationQualifier+relationQualifier+suffRelationQualifier;
		String label = conceptRole.getPrefLabel(this.userLanguage, strictLanguage);
		if (label != null && !label.isEmpty()) label = label+":";
		String icon = conceptRole.getIconAdapted(this);
		if (icon != null && !icon.isEmpty()) {
			return prefRelationQualifier+this.toHtmlImage(icon, label, false)+suffRelationQualifier;
		} else if (label != null) {
			return prefRelationQualifier+Util.escapeHTMLEntities(label)+suffRelationQualifier;
		} else  {
			return prefRelationQualifier+relationQualifier+suffRelationQualifier;
		}
	}

	private String getRequestLanguage() {
		if (this.request == null) return TermList.ENGLISH;
		Locale aLocale = this.request.getLocale();
		if (aLocale == null) return TermList.ENGLISH;
		return aLocale.getLanguage();
	}

	public String getLanguage() {
		return userLanguage;
	}

	public String getProfile() {
		return userProfile;
	}

	public void setProfile(String profile) {
		if (profile.toLowerCase().startsWith(skosManager.aboutProfiles+SchemeInterface.separator)) {
			profile = profile.substring(skosManager.aboutProfiles.length()+1);
		}
		this.userProfile = profile;
	}

	private String getRequestProfile() {
		if (this.request != null) {
			String aProfile = this.request.getParameter("profile");
			if (aProfile != null && aProfile.length() > 0) {
				setProfile(aProfile);
				this.request.getSession().setAttribute("profile", this.userProfile);
			} else {
				aProfile = (String) this.request.getSession().getAttribute("profile");
				if (aProfile != null && aProfile.length() > 0) {
					setProfile(aProfile);
				}
			}
		}
		return this.userProfile;
	}

	/**
	 * @return the request
	 */
	public HttpServletRequest getRequest() {
		return request;
	}

	/**
	 * @param request the request to set
	 */
	public void setRequest(HttpServletRequest request) {
		this.request = request;
	}

	/**
	 * @return the skosManager
	 */
	public SkosManager getSkosManager() {
		return skosManager;
	}

	/**
	 * @param skosManager the skosManager to set
	 */
	public void setSkosManager(SkosManager skosManager) {
		this.skosManager = skosManager;
	}

	/**
	 * @return the application
	 */
	public String getApplication() {
		return application;
	}

	/**
	 * @param application the application to set
	 */
	public void setApplication(String application) {
		this.application = application;
	}

	/**
	 * @return the framedInAskosi
	 */
	public boolean isFramedInAskosi() {
		return framedInAskosi;
	}

	/**
	 * @param framedInAskosi the framedInAskosi to set
	 */
	public void setFramedInAskosi(boolean framedInAskosi) {
		this.framedInAskosi = framedInAskosi;
	}
	
	public String addFraming(String baseUrl) {
		if (this.isFramedInAskosi()) {
			String suffix = "";
			int posD = baseUrl.indexOf('#');
			if (posD >= 0) {
				suffix = baseUrl.substring(posD);
				baseUrl = baseUrl.substring(0, posD);
			}
			if (baseUrl.indexOf('?') >= 0) {
				return baseUrl+'&'+framingUrlSuffix+suffix;
			} else {
				return baseUrl+'?'+framingUrlSuffix+suffix;
			}
		} else return baseUrl;

	}
	
	public final String askosiIcon(String iconName) {
		SchemeInterface iconScheme = skosManager.openScheme(askosiMessagesScheme);
		Concept anIconMessage = null;
		if (iconScheme == null || iconScheme.getClass() == NoScheme.class) {
			log.error("ASKOSI Icons and Messages Scheme not found when icon "+iconName+" was needed.");
		} else {
			anIconMessage = iconScheme.getConcept(iconName);
		}
		StringBuffer imageSrc = new StringBuffer();
		imageSrc.append(" src=\"");
		if (anIconMessage == null) {
			imageSrc.append(adaptURL(getApplication(),"[root"
					+SkosHtmlManager.askosiDisplayApplication
					+"]/image/"
					+iconName
					+".png"));
			imageSrc.append("\" title=\"");
			imageSrc.append(iconName);
			imageSrc.append("\"");
		} else {
			imageSrc.append(adaptURL(getApplication(),anIconMessage.getIcon(getLanguage())));
			imageSrc.append("\" title=\"");
			imageSrc.append(Util.escapeHTMLEntities(anIconMessage.getPrefLabel(getLanguage(), strictLanguage)));
			imageSrc.append("\"");
		}
		return imageSrc.toString();
	}

	public String askosiMessage(String messageName) {
		SchemeInterface iconScheme = skosManager.openScheme(askosiMessagesScheme);
		Concept anIconMessage = null;
		if (iconScheme == null || iconScheme.getClass() == NoScheme.class) {
			log.error("ASKOSI Messages Scheme not found when message "+messageName+" was needed.");
		} else {
			anIconMessage = iconScheme.getConcept(messageName);
		}
		if (anIconMessage == null) {
			return messageName;
		} else {
			return Util.escapeHTMLEntities(anIconMessage.getPrefLabel(getLanguage(), strictLanguage));
		}
	}

	public void setLanguage(String language) {
		this.userLanguage = language;
	}

	/*
	 * Generate a button to call edit tools on a given statement
	 * A JavaScript function "editStatement" is called with an object in parameter containing the following fields:
	 * about: scheme_about of the current concept
	 * property: Edited SKOS property 
	 * concept: scheme_about of the linked concept
	 * notation: notationScheme_about of the linked concept
	 * literallanguage and literal: the linked information is a Label, a Note or an URL
	 */
	public String toHtmlEdit(Concept aConcept, String property /*e.g."narrowMatch"*/, StatusInterface anObject) {
		boolean adding = anObject == null;
		String statusIcon = "";
		String params = "";
		Status aStatus = Status.New_Proposed;
		if (!adding) {
			aStatus = anObject.getCurrentStatus();
			params = anObject.toJavaScriptParameters(); 
		} else {
			params = ",adding:true";
		}
		if (aStatus != null) {
			statusIcon = aStatus.toHtml(this);
		} else {
			statusIcon = "<img"	+askosiIcon("edit")	+" border=\"0\"/>"; 
		}
		if (adding) {
			statusIcon = statusIcon.concat(property);
		}
/*
		String askosiRoot = this.getApplicationRoot(SkosHtmlManager.askosiDisplayApplication);
		String url = this.addFraming(Reflector.substituteURL(askosiRoot, aConcept, "[root]/editconcept.jsp?about=[scheme_About]", this.getLanguage()));
		return "<a class=\"SkosEditButton\" href=\""
		+url
		+"&property="
		+property
		+anObject.toRdfObjectUrl()
		+"\">"
		+statusIcon
		+"</a>";
*/
		return
		"<a class=\"SkosEditButton\" href=\"#"
		+property
		+"\" onclick=\"javascript:editStatement(event,{about:'"
		+aConcept.getScheme_About()
		+"',property:'"
		+property
		+"'"
		+params
		+"})\">"
		+statusIcon
		+"</a>";
	}

	static final String sepStyle = ""; // " style=\"border-top:1px solid black !important\""; DOES NOT WORK!!!
	
	static final boolean strictLanguage = true;

	public String toHtmlSurroundings (String aboutScheme, String conceptName, int maxRel, boolean printing, boolean basketEnabled) {
		// maxRel > 0 means all specifics and relations are displayed
		// maxRel = 0 means no specifics are displayed but other relations are
		// maxRel < 0 means no specifics and no relation apart from broader are displayed
		StringBuffer res = new StringBuffer();
		ConceptScheme aScheme = null;
		Concept singleConcept = null;
		ArrayList<Concept> result = null;
		/* if aboutScheme is a full concept reference... */
		if (aboutScheme.indexOf(SchemeInterface.separator) >= 0) {
			singleConcept = skosManager.getScheme_Concept(aboutScheme);
			if (singleConcept != null) {
				result = new ArrayList<Concept>();
				result.add(singleConcept);
				aScheme = singleConcept.getInScheme();
			}
		} else {
			/* if we must find the concept by its name... */
			aScheme = (ConceptScheme)skosManager.getScheme(aboutScheme);
			if (aScheme != null) {
				ConceptScheme[] schemes = new ConceptScheme[1];
				schemes[0] = aScheme;
				result = SkosSearch.search(this,strictLanguage,schemes, conceptName, LabelMatchType.EXACT, null);
				if (result == null || result.isEmpty() ) {
					result = SkosSearch.search(this,strictLanguage,schemes, conceptName, LabelMatchType.STARTS_WITH, null);
					if (result == null || result.isEmpty() ) {
						result = SkosSearch.search(this,strictLanguage,schemes, conceptName, LabelMatchType.INDEPENDANT_PIECES, null);
					}
				}
			}
		}
		if (result != null && !result.isEmpty() ) {
			String listPrefix = "";
			String listSuffix = "";
			if (printing) {
				listPrefix = "<li><img border=\"0\" src=\"";
				listSuffix = "\"/>";
				for(Concept aConcept: result) {
					res.append("<ul>");
					LinkedList<LinkedConcept> btms = aConcept.getBroadMatch();
					if (btms != null && btms.size() > 0) {
						for(LinkedConcept aboutBT : btms) {
							if (aboutBT == null) continue;
							Concept aBT = aboutBT.getConcept(skosManager);
							if (aBT == null) continue;
							String aLabel = aBT.toHtmlLabel(this);
							res.append(listPrefix);
							res.append( Predicate.Concept_broadMatch.toHtmlImage(this) );
							res.append(toHtmlRelationQualifier(aboutBT.getRelationQualifier()));
							res.append(listSuffix);
							res.append( aLabel );
							//res.append( "</li>\n" );
						} }
					LinkedList<LocalLinkedConcept> bts = aConcept.getBroader();
					if (bts != null && bts.size() > 0) {
						for(LocalLinkedConcept aboutBT: bts) {
							Concept aBT = aboutBT.getConcept(aScheme);
							if (aBT == null) continue;
							String aLabel = aBT.toHtmlLabel(this);
							res.append(listPrefix);
							res.append( Predicate.Concept_broader.toHtmlImage(this) );
							res.append(listSuffix);
							res.append(toHtmlRelationQualifier(aboutBT.getRelationQualifier()));
							res.append( aLabel );
							//res.append( "</li>\n" ); 
						} }
					res.append("<ul>\n");
					String display = aConcept.toHtmlLabel(this);
					res.append( "<li><h1>" );
					res.append( display );
					res.append( "</h1>\n" );

					res.append("<ul>\n");
					LinkedList<LocalLinkedConcept> nts = aConcept.getNarrower();
					if (nts != null && nts.size() > 0) {
						if (nts.size() > maxRel) {
							res.append(listPrefix);
							res.append( Predicate.Concept_narrower.toHtmlImage(this) );
							res.append(listSuffix);
							res.append( nts.size() );
							res.append( "...</li>\n" );
						} else {
							for(LocalLinkedConcept aboutNT: nts) {
								Concept aNT = aboutNT.getConcept(aScheme);
								if (aNT == null) continue;
								String aLabel = aNT.toHtmlLabel(this);
								res.append(listPrefix);
								res.append( Predicate.Concept_narrower.toHtmlImage(this) );
								res.append(listSuffix);
								res.append(toHtmlRelationQualifier(aboutNT.getRelationQualifier()));
								res.append( aLabel );
								res.append( "</li>\n" ); 
							} } }
					LinkedList<LinkedConcept> ntms = aConcept.getNarrowMatch();
					if (ntms != null && ntms.size() > 0) {
						if (ntms.size() > maxRel) {
							res.append(listPrefix);
							res.append( Predicate.Concept_narrowMatch.toHtmlImage(this) );
							res.append(listSuffix);
							res.append( ntms.size() );
							res.append( "...</li>\n" );
						} else {
							for(LinkedConcept aboutNT : ntms) {
								if (aboutNT == null) continue;
								Concept aNT = aboutNT.getConcept(skosManager);
								if (aNT == null) continue;
								String aLabel = aNT.toHtmlLabel(this);
								res.append(listPrefix);
								res.append( Predicate.Concept_narrowMatch.toHtmlImage(this) );
								res.append(listSuffix);
								res.append(toHtmlRelationQualifier(aboutNT.getRelationQualifier()));
								res.append( aLabel );
								res.append( "</li>\n" );
							} } }
					res.append("</ul>\n");
					LinkedList<LinkedConcept> eqms = aConcept.getExactMatch();
					if (eqms != null && eqms.size() > 0) {
						for(LinkedConcept aboutEQ : eqms) {
							if (aboutEQ == null) continue;
							Concept aEQ = aboutEQ.getConcept(skosManager);
							if (aEQ == null) continue;
							String aLabel = aEQ.toHtmlLabel(this);
							res.append(listPrefix);
							res.append( Predicate.Concept_exactMatch.toHtmlImage(this) );
							res.append(listSuffix);
							res.append(toHtmlRelationQualifier(aboutEQ.getRelationQualifier()));
							res.append( aLabel );
							res.append( "</li>\n" ); 
						} }
					eqms = aConcept.getCloseMatch();
					if (eqms != null && eqms.size() > 0) {
						for(LinkedConcept aboutEQ : eqms) {
							if (aboutEQ == null) continue;
							Concept aEQ = aboutEQ.getConcept(skosManager);
							if (aEQ == null) continue;
							String aLabel = aEQ.toHtmlLabel(this);
							res.append(listPrefix);
							res.append( Predicate.Concept_closeMatch.toHtmlImage(this) );
							res.append(listSuffix);
							res.append(toHtmlRelationQualifier(aboutEQ.getRelationQualifier()));
							res.append( aLabel );
							res.append( "</li>\n" ); 
						} }
					LinkedList<LocalLinkedConcept> rts = aConcept.getRelated();
					if (rts != null && rts.size() > 0) {
						for(LocalLinkedConcept aboutRT: rts) {
							Concept aRT = aboutRT.getConcept(aScheme);
							if (aRT == null) continue;
							String aLabel = aRT.toHtmlLabel(this);
							res.append(listPrefix);
							res.append( Predicate.Concept_related.toHtmlImage(this) );
							res.append(listSuffix);
							res.append(toHtmlRelationQualifier(aboutRT.getRelationQualifier()));
							res.append( aLabel );
							res.append( "</li>\n" ); 
						} }
					LinkedList<LinkedConcept> rtms = aConcept.getRelatedMatch();
					if (rtms != null && rtms.size() > 0) {
						for(LinkedConcept aboutRT : rtms) {
							if (aboutRT == null) continue;
							Concept aRT = aboutRT.getConcept(skosManager);
							if (aRT == null) continue;
							String aLabel = aRT.toHtmlLabel(this);
							res.append(listPrefix);
							res.append( Predicate.Concept_relatedMatch.toHtmlImage(this) );
							res.append(listSuffix);
							res.append(toHtmlRelationQualifier(aboutRT.getRelationQualifier()));
							res.append( aLabel );
							res.append( "</li>\n" ); 
						} }
					res.append("</ul>\n</ul>\n");
				}
			} else /* NOT PDF printing: screen display! */ {
				SkosHtmlTable skosHtmlTable = new SkosHtmlTable(this);
				skosHtmlTable.setCategorized(false);
				skosHtmlTable.setBasket(basketEnabled);
				for (Concept aConcept: result) {
					aScheme = aConcept.getInScheme();
					skosHtmlTable.putScheme(aScheme);
				}
				res.append( "<table cellspacing=\"0\" class=\"tableWithFloatingHeader\"><col/>\n");
				res.append(skosHtmlTable.toHtmlColGroups());
				skosHtmlTable.initRow();
				res.append( "<tr class=\"");
				res.append( skosHtmlTable.nextRowClass() );
				res.append( "\">" );
				res.append( "<th align=\"left\">" );
				if (singleConcept == null) {
					res.append("&nbsp;");
				} else {
					res.append( "<a href=\"" );
					if (maxRel > 0) {
						res.append(singleConcept.getAskosiUrl(this, "&expanded=yes"));
						res.append( "\">");
						res.append( this.askosiMessage("expand") );
					} else {
						res.append(singleConcept.getAskosiUrl(this, ""));
						res.append( "\">");
						res.append( this.askosiMessage("view") );						
					}
					res.append("</a>");					
				}
				res.append( "</th>\n");
				res.append( skosHtmlTable.toHtmlApplicationHeaders());
				res.append( "</tr>\n" );
				res.append( "<tr class=\"");
				res.append( skosHtmlTable.nextRowClass() );
				res.append( "\">"); 
				res.append( "<th align=\"left\">&nbsp;</th>\n" );
				res.append( skosHtmlTable.toHtmlRoleHeaders() );
				res.append( "</tr>\n");
				for(Concept aConcept: result) {
					String display = aConcept.toHtmlLabel(this);
					LinkedList<LinkedConcept> btms = aConcept.getBroadMatch();
					if (btms != null && btms.size() > 0) {
						for(LinkedConcept aboutBT : btms) {
							if (aboutBT == null) continue;
							Concept aBT = aboutBT.getConcept(skosManager);
							if (aBT == null) continue;
							String aLabel = aBT.toHtmlLabel(this);
							res.append( "<tr class=\"" );
							res.append( skosHtmlTable.nextRowClass() );
							res.append( "\">\n<td class=\"SkosTableConcept\"><a class=\"linkaction\" href=\"" );
							res.append(aBT.getAskosiUrl(this, "&amp;graf="+getLanguage()));
							res.append( "#Graf\">");
							res.append( Predicate.Concept_broadMatch.toHtmlIcon(this) );
							res.append( "</a>");
							res.append(toHtmlRelationQualifier(aboutBT.getRelationQualifier()));
							res.append( "<a href=\"" );
							res.append(aBT.getAskosiUrl(this, ""));
							res.append( "\">");
							res.append( aLabel );
							res.append( "</a></td>\n" );
							res.append( skosHtmlTable.toHtmlColumns(aBT) );
							res.append( "</tr>\n" );
						} 
					}
					LinkedList<LocalLinkedConcept> bts = aConcept.getBroader();
					if (bts != null && bts.size() > 0) {
						for(LocalLinkedConcept aboutBT: bts) {
							Concept aBT = aboutBT.getConcept(aScheme);
							if (aBT == null) continue;
							String aLabel = aBT.toHtmlLabel(this);
							res.append( "<tr class=\"" );
							res.append( skosHtmlTable.nextRowClass() );
							res.append( "\">\n<td class=\"SkosTableConcept\"><a class=\"linkaction\" href=\"" );
							res.append(aConcept.getAskosiUrl(this, "&amp;graf="+getLanguage()+"&amp;grafocus="+aBT.getScheme_About()));
							res.append( "#Graf\">");
							res.append( Predicate.Concept_broader.toHtmlIcon(this) );
							res.append( "</a>");
							res.append(toHtmlRelationQualifier(aboutBT.getRelationQualifier()));
							res.append( "<a href=\"" );
							res.append(aBT.getAskosiUrl(this, ""));
							res.append( aLabel );
							res.append( "</a></td>\n" );
							res.append( skosHtmlTable.toHtmlColumns(aBT) );
							res.append( "</tr>\n" );
						} 
					}
					res.append( "<tr class=\"" );
					res.append( skosHtmlTable.nextRowClass() );
					res.append( "\">\n<td class=\"SkosTableConcept\" style=\"padding-left:26px\"><big><strong>" );
					res.append( display );
					res.append( "</strong></big></td>\n" );
					res.append( skosHtmlTable.toHtmlColumns(aConcept) );
					res.append( "</tr>\n" );
					if (maxRel >= 0) {
						boolean someNTs = true; // now, always an horizontal lines before the RTs
						LinkedList<LocalLinkedConcept> nts = aConcept.getNarrower();
						if (nts != null && nts.size() > 0) {
							someNTs = true;
							if (nts.size() > maxRel) {
								res.append( "<tr class=\"" );
								res.append( skosHtmlTable.nextRowClass() );
								res.append( "\">\n<td class=\"SkosTableConcept\" style=\"padding-left:56px\">" );
								res.append( Predicate.Concept_narrower.toHtmlIcon(this) );
								res.append( nts.size() );
								res.append( "...</td>\n" );
								res.append( skosHtmlTable.toHtmlColumns((Concept)null) );
								res.append( "</tr>\n" );
							} else {
								int i=0;
								for(LocalLinkedConcept aboutNT: nts) {
									Concept aNT = aboutNT.getConcept(aScheme);
									if (aNT == null) continue;
									String aLabel = aNT.toHtmlLabel(this);
									res.append( "<tr class=\"" );
									res.append( skosHtmlTable.nextRowClass() );
									res.append( "\">\n<td class=\"SkosTableConcept\" style=\"padding-left:56px\">" );
									/* res.append("<img");
									i++;
									res.append( this.askosiIcon("narrow_"+(i==nts.size()?"last_":"")+"element"));
									res.append('>'); */
									res.append( Predicate.Concept_narrower.toHtmlIcon(this) );
									res.append(toHtmlRelationQualifier(aboutNT.getRelationQualifier()));
									res.append( "<a href=\"" );
									res.append(aNT.getAskosiUrl(this, ""));
									res.append( aLabel );
									res.append( "</a></td>\n" ); 
									res.append( skosHtmlTable.toHtmlColumns(aNT) );
									res.append( "</tr>\n" );
								}
							} 
						}
						LinkedList<LinkedConcept> ntms = aConcept.getNarrowMatch();
						if (ntms != null && ntms.size() > 0) {
							someNTs = true;
							if (ntms.size() > maxRel) {
								res.append ( "<tr class=\"" );
								res.append( skosHtmlTable.nextRowClass() );
								res.append( "\">\n<td class=\"SkosTableConcept\" style=\"padding-left:56px\">" );
								res.append( Predicate.Concept_narrowMatch.toHtmlIcon(this) );
								res.append( ntms.size() );
								res.append( "...</td>\n" );
								res.append( skosHtmlTable.toHtmlColumns((Concept)null) );
								res.append( "</tr>\n" );
							} else {
								int i=0;
								for(LinkedConcept aboutNT : ntms) {
									if (aboutNT == null) continue;
									Concept aNT = aboutNT.getConcept(skosManager);
									if (aNT == null) continue;
									String aLabel = aNT.toHtmlLabel(this);
									res.append( "<tr class=\"<%=curClass%>\">\n<td class=\"SkosTableConcept\" style=\"padding-left:56px\">" );
									/*res.append("<img");
									i++;
									res.append( this.askosiIcon("narrow_"+(i==ntms.size()?"last_":"")+"element"));
									res.append('>');*/
									res.append( Predicate.Concept_narrowMatch.toHtmlIcon(this) );
									res.append(toHtmlRelationQualifier(aboutNT.getRelationQualifier()));
									res.append( "<a href=\"" );
									res.append(aNT.getAskosiUrl(this, ""));
									res.append( aLabel );
									res.append( "</a></td>\n" );
									res.append( skosHtmlTable.toHtmlColumns(aNT) );
									res.append( "</tr>\n" );
								}
							} 
						}
						LinkedList<LinkedConcept> rtms = aConcept.getExactMatch();
						if (rtms != null && rtms.size() > 0) {
							for(LinkedConcept aboutRT : rtms) {
								if (aboutRT == null) continue;
								Concept aRT = aboutRT.getConcept(skosManager);
								if (aRT == null) continue;
								String aLabel = aRT.toHtmlLabel(this);
								res.append( "<tr class=\"" );
								res.append( skosHtmlTable.nextRowClass() );
								res.append( "\"" );
								if (someNTs) {
									res.append(sepStyle);
									someNTs = false;
								}
								res.append(">\n<td class=\"SkosTableConcept\" style=\"padding-left:26px\">");
								res.append( Predicate.Concept_exactMatch.toHtmlIcon(this) );
								res.append(toHtmlRelationQualifier(aboutRT.getRelationQualifier()));
								res.append( "<a href=\"" );
								res.append(aRT.getAskosiUrl(this, ""));
								res.append( aLabel );
								res.append( "</a></td>\n" ); 
								res.append( skosHtmlTable.toHtmlColumns(aRT) );
								res.append( "</tr>\n" );
							} 
						}
						rtms = aConcept.getCloseMatch();
						if (rtms != null && rtms.size() > 0) {
							for(LinkedConcept aboutRT : rtms) {
								if (aboutRT == null) continue;
								Concept aRT = aboutRT.getConcept(skosManager);
								if (aRT == null) continue;
								String aLabel = aRT.toHtmlLabel(this);
								res.append( "<tr class=\"" );
								res.append( skosHtmlTable.nextRowClass() );
								res.append( "\"" );
								if (someNTs) {
									res.append(sepStyle);
									someNTs = false;
								}
								res.append(">\n<td class=\"SkosTableConcept\" style=\"padding-left:26px\">");
								res.append( Predicate.Concept_closeMatch.toHtmlIcon(this) );
								res.append(toHtmlRelationQualifier(aboutRT.getRelationQualifier()));
								res.append( "<a href=\"" );
								res.append(aRT.getAskosiUrl(this, ""));
								res.append( aLabel );
								res.append( "</a></td>\n" ); 
								res.append( skosHtmlTable.toHtmlColumns(aRT) );
								res.append( "</tr>\n" );
							} 
						}
						LinkedList<LocalLinkedConcept> rts = aConcept.getRelated();
						if (rts != null && rts.size() > 0) {
							for(LocalLinkedConcept aboutRT: rts) {
								Concept aRT = aboutRT.getConcept(aScheme);
								if (aRT == null) continue;
								String aLabel = aRT.toHtmlLabel(this);
								res.append( "<tr class=\"" );
								res.append( skosHtmlTable.nextRowClass() );
								res.append( "\"" );
								if (someNTs) {
									res.append(sepStyle);
									someNTs = false;
								}
								res.append(">\n<td class=\"SkosTableConcept\" style=\"padding-left:26px\">");
								res.append( Predicate.Concept_related.toHtmlIcon(this) );
								res.append(toHtmlRelationQualifier(aboutRT.getRelationQualifier()));
								res.append( "<a href=\"" );
								res.append(aRT.getAskosiUrl(this, ""));
								res.append( aLabel );
								res.append( "</a></td>\n" ); 
								res.append( skosHtmlTable.toHtmlColumns(aRT) );
								res.append( "</tr>\n" );                           
							} }
						rtms = aConcept.getRelatedMatch();
						if (rtms != null && rtms.size() > 0) {
							for(LinkedConcept aboutRT : rtms) {
								if (aboutRT == null) continue;
								Concept aRT = aboutRT.getConcept(skosManager);
								if (aRT == null) continue;
								String aLabel = aRT.toHtmlLabel(this);
								res.append( "<tr class=\"" );
								res.append( skosHtmlTable.nextRowClass() );
								res.append( "\"" );
								if (someNTs) {
									res.append(sepStyle);
									someNTs = false;
								}
								res.append(">\n<td class=\"SkosTableConcept\" style=\"padding-left:26px\">");
								res.append( Predicate.Concept_relatedMatch.toHtmlIcon(this) );
								res.append(toHtmlRelationQualifier(aboutRT.getRelationQualifier()));
								res.append( "<a href=\"" );
								res.append(aRT.getAskosiUrl(this, ""));
								res.append( aLabel );
								res.append( "</a></td>\n" ); 
								res.append( skosHtmlTable.toHtmlColumns(aRT) );
								res.append( "</tr>\n" );
							} 
						}
					}
				}
				res.append( "<tr class=\"floatingHeaders\"><th align=\"left\">&nbsp;</th>\n" );
				res.append( skosHtmlTable.toHtmlRoleHeaders() );
				res.append( "</tr>\n");
				res.append( "</table>" );
			}
/*		} else {
			res.append("Not found: ");
			res.append(aboutScheme);
			res.append(':');
			res.append(conceptName);
*/		}
		return res.toString();
	}

	public Profile getProfileData() {
		if (userProfile == null || userProfile.isEmpty()) {
			userProfile = Profile.guest;
		}
		Profile currProfile = Profile.getInstance(skosManager, userProfile);
		if (currProfile == null) Profile.getInstance(skosManager, Profile.guest);
		return currProfile;
	}

	public String toHtmlSource(String aSource) {
		if (aSource == null || aSource.isEmpty()) return "";
		ConceptScheme theSources = skosManager.getSources();
		//if (application == null) application = this.application;
		if (theSources == null) return prefSource+aSource+suffSource;
		Concept conceptSource = theSources.getConcept(aSource);
		if (conceptSource == null) return prefSource+aSource+suffSource;
		String label = conceptSource.getPrefLabel(this.userLanguage, strictLanguage);
		String icon = conceptSource.getIconAdapted(this);
		if (icon != null && !icon.isEmpty()) {
			return prefSource+toHtmlImage(icon, label, false)+suffSource;
		} else if (label != null && !label.isEmpty()) {
			return prefSource+Util.escapeHTMLEntities(label)+suffSource;
		} else  {
			return prefSource+aSource+suffSource;
		}
	}

	public static SkosHtmlManager getAskosiSessionInstance (HttpServletRequest request) {
		SkosHtmlManager skosHtmlManager = getSessionInstance (request, askosiDisplayApplication); 
		if (skosHtmlManager != null) {
			String toPreload = Util.getInitParameter(request,SKOSpreloadParam);
			if (toPreload != null) {
				StringTokenizer st = new StringTokenizer(toPreload,";\n, ");
				while (st.hasMoreTokens()) {
					String codeScheme = st.nextToken();
					skosHtmlManager.getSkosManager().openScheme(codeScheme);
				}
			}
		}
		return skosHtmlManager;
	}

	/**
	 * Get a SkosHtmlManager for the current request and the application name.
	 * It takes into account SKOSdirectory context parameter in the WEB-INF/web.xml
	 * It also takes into account the language associated with the request.
	 * 
	 * @param request : current HttpServletRequest
	 * @param application : identifier of the current application
	 * @return the SkosHtmlManager to access ASKOSI functions.
	 */
	public static SkosHtmlManager getSessionInstance (HttpServletRequest request, String application) {
		HttpSession session = request.getSession();
		if (session == null) return null;
		String userLanguage = null;
        String paramLocale = request.getParameter("locale");
        if (paramLocale != null && paramLocale != "")
        { /* get session locale according to user selection */
        	userLanguage = paramLocale;
        } else {
            Locale sessionLocale = (Locale) session.getAttribute("locale");
            if (sessionLocale != null) {
            	userLanguage = sessionLocale.getLanguage();
            }
        }

        String skosDirectory = null;
		Object anAttribute = session.getAttribute(SKOSdirectoryParam);
		if (anAttribute != null && anAttribute instanceof String) {
			skosDirectory = (String)anAttribute;
		} else {
			skosDirectory = request.getParameter(SKOSdirectoryParam);
			if (skosDirectory == null) {
				skosDirectory = Util.getInitParameter(request,SKOSdirectoryParam);
			}
			session.setAttribute(SKOSdirectoryParam, skosDirectory);
		}
		if (skosDirectory == null) return null;
		
		SkosHtmlManager skosHtmlManager = new SkosHtmlManager(request,userLanguage,skosDirectory,application);
		String applicationProxy = Util.getInitParameter(request,SkosManager.ApplicationProxyParam);
		skosHtmlManager.getSkosManager().setApplicationProxy(applicationProxy);
		return skosHtmlManager;
	}

	public static String graphVizExecCache = null;

	/**
	 * Return the path to GraphViz. If it is in the PATH of the machine, it finds it automatically.
	 * 
	 * @param request: current HttpServletRequest if access to ServletContext is needed
	 * @return
	 */
	public static synchronized String getGraphVizExec (HttpServletRequest request) {
		if (graphVizExecCache != null) return graphVizExecCache;

		String exec = null;
		exec = Util.getInitParameter(request,GraphVizExecParam);
		if (exec == null) {
			String pathString = System.getenv("PATH");
			String[] paths = pathString.split("\\"+File.pathSeparatorChar);
			for (int i=0; i<paths.length; i++) {
				if (paths[i].toLowerCase().indexOf("graphviz") >= 0) {
					exec = paths[i];
					break;
				}
			}
			if (exec != null) {
				File dir = new File(exec);

				// It is also possible to filter the list of returned files.
				// Returns any file that start with "dot" but is not "dotty"
				FilenameFilter filter = new FilenameFilter() {
				    public boolean accept(File dir, String name) {
				    	if (name.startsWith("dot")) {
				    		if (name.length()==3) return true;
				    		if (Character.isJavaIdentifierPart(name.charAt(3))) {
				    			return false;
				    		}
				    		return true;
				    	}
				        return false;
				    }
				};
				String [] children = dir.list(filter);
				if (children == null || children.length == 0) {
				    // Either dir does not exist or is not a directory
					exec = null;
				} else {
				    exec = exec+File.separatorChar+children[0];
				}



			}
		}
		graphVizExecCache = exec;
		return exec; 
	}

	/**
	 * This returns the directory where GraphViz files are generated.
	 * This can be parameterized as GraphVizDirectory context parameter in WEB.XML
	 * but the calculated value ( graf subdirectory in the ASKOSI "scheme" webapp) is good.
	 * 
	 * @param request : current HttpServletRequest (used to get ServletContext)
	 * @return
	 */
	public static String getGraphVizDirectory (HttpServletRequest request) {
		String directory = null;
		ServletContext context = request.getServletContext();
        if (context != null) {
            directory = Util.getInitParameter(request,GraphVizDirectoryParam);
			if (directory == null || directory.isEmpty()) {
				directory = context.getRealPath("/graf")+File.separatorChar;
				if (directory == null) { // GetRealPath does not work (application in a War?
					log.error("GraphVizDirectory must be set as a context-param in WEB.XML when deploying with a .WAR");
				}
			}
		}
		return directory;
	}

	/**
	 * This procedure keeps in the Session attribute "recentConcepts" a LinkedHashSet of the concepts recently visited by the user
	 * 
	 * @param request: current HttpServletRequest
	 * @param scheme_about : ConceptScheme_Concept of the accessed concept
	 * @param limit : maximum number of concepts to keep in the list
	 * @return : an array of the ConceptScheme_Concept recently accessed
	 */
	public static String[] maintainRecentConcepts (HttpServletRequest request, String scheme_about, int limit) {
		HttpSession session = request.getSession();
		if (session == null) return null;
		synchronized (session) {
			LinkedHashSet<String> recentDisplay = null;
			Object recentSessionDisplay = session.getAttribute("recentConcepts");
			if (recentSessionDisplay != null && recentSessionDisplay instanceof LinkedHashSet) {
				recentDisplay = (LinkedHashSet<String>) recentSessionDisplay;
			} else {
				recentDisplay = new LinkedHashSet<String>();
				request.getSession().setAttribute("recentConcepts",recentDisplay);
			}
			if (scheme_about != null && !scheme_about.isEmpty()) {
				recentDisplay.remove(scheme_about);
				recentDisplay.add(scheme_about);
			}
			String[] recentConcepts = new String[recentDisplay.size()];
			recentDisplay.toArray(recentConcepts);
			if (limit > 0 && recentConcepts.length > limit) {
				int i=0;
				while (i < (limit-recentConcepts.length)) {
					String anAbout = recentConcepts[i];
					recentDisplay.remove(anAbout);
					i++;
				}
				return Arrays.copyOfRange(recentConcepts, i, recentConcepts.length-1);
			} else return recentConcepts;
		}
	}
	
	public String getAtomURL(Concept aConcept) {
		return this.getApplicationRoot(askosiDisplayApplication)+"/concept.svc/"+aConcept.getInScheme().getAbout()+'('+aConcept.getAbout()+')';
	}

	public static String getAskosiSearchRelation(SkosManager skosManager, String relation) {
		return skosManager.getApplicationRoot(askosiDisplayApplication)+"/concept.jsp?concept=[scheme_About]&relfocus="+relation+"#"+relation;
	}

	public static String getAskosiAtomRelation(SkosManager skosManager, String relation) {
		return skosManager.getApplicationRoot(askosiDisplayApplication)+"/concept.svc/[aboutScheme]([about])/$links/"+relation;
	}
	
	/*
	 * Generates a select menu to choose within a set of Concepts.
	 * The select begin and end must be generated by the caller (only options are generated here).
	 * <select name=\"desired parameter name"\"><option></option> (if an empty choice is allowed)
	 * ...
	 * </select>
	 */
	public String toHtmlSelect(Collection<Concept> conceptSet, String currScheme_About) {
        String result = ""; 
        for (Concept oneConcept: conceptSet) {
            result = result
                    + "<option"
                    +( oneConcept.getScheme_About().equals(currScheme_About) ? " selected" : "")
                    +" value=\""+oneConcept.getScheme_About()+"\">"
                    +oneConcept.getPrefLabel(this.getLanguage(), strictLanguage)
                    +"</option>";
        }
        return result;
	}
}
